###########################################################################
# Happy Hare supporting macros
#   Standalone Tip Cutting for "Filametrix" style toolhead cutters
#
# To configure, set
#   'form_tip_macro: _MMU_CUT_TIP' in 'mmu_parameters.cfg'
#
# Default configuration is good for Stealthburner with CW2 and Voron Revo nozzle
#
# IMPORTANT:
#   The park position of the filament is relative to the nozzle and
#   represents where the end of the filament is after cutting. The park position
#   is important and used by Happy Hare both to finish unloading the extruder
#   as well as to calculate how far to advance the filament on the subsequent load.
#
# When using this macro it is important to turn off tip forming in your slicer and
# force Happy Hare to always run this when loading filament by adding:
#   'force_form_tip_standalone: 1' in 'mmu_parameters.cfg'
# Also decide on 'return_to_wipetower' (here) vs 'enable_park' (mmu_sequence.cfg)
#
# Implementation note: It is important to report back the position the cutter
# leaves the filament in the extruder via the variable 'output_park_pos' so this
# is set dynamically in gcode with this construct:
#   SET_GCODE_VARIABLE MACRO=_MMU_CUT_TIP VARIABLE=output_park_pos VALUE=..
#
[gcode_macro _MMU_CUT_TIP]
description: Cut filament by pressing the cutter on a pin with a horizontal movement

# By default this macro will return toolhead to initial position (usually wipetower) after the cut is complete.
# If using parking logic you may want to disable this and set 'park_after_form_tip:1' in mmu_sequence.cfg
variable_restore_position: 1		# 1 = return to initial position after cut, 0 = don't return

# Distance from the internal nozzle tip to the cutting blade. This dimension is based on your
# toolhead and should not be used for tuning.
variable_blade_pos: 37.5

# Distance to retract prior to making the cut, this reduces wasted filament (left behind in extruder)
# but might cause clog if set too large and/or if there are gaps in the hotend assembly 
# This must be less than 'blade_pos', 5mm less is a good starting point
variable_retract_length: 32.5

# This should be the position of the toolhead where the cutter arm just lightly touches the pin
variable_pin_loc_x: 14			# Location of depressor pin. Must set
variable_pin_loc_y: 250			# Location of depressor pin. Must set

# This distance is added to 'pin_loc_x' to determine the starting position and to create a small saftely
# distance that aids in generating momentum
variable_pin_park_x_dist: 5.0

# Position of the toolhead when the cutter is fully compressed
# Should leave a small headroom (should be a bit larger than 0, or whatever xmin is) to avoid banging the toolhead or gantry
variable_pin_loc_x_compressed: 0.5

# Retract length and speed after the cut so that the cutter blade doesn't get stuck on return to origin position
variable_rip_length: 1			# Distance to retract to aid lever decompression (>= 0)
variable_rip_speed: 12			# mm/s

# Pushback of the remaining tip from the cold end into the hotend. This does not have to push back all the way, just
# sufficient to ensure filament fragment stays in hot end. Cannot be larger than 'retract_length'
variable_pushback_length: 5
variable_pushback_dwell_time: 0		# Time to dwell after the pushback

# Speed related settings
# Note that if the cut speed is too fast, the steppers can lose steps. Therefore, for a cut: 
# - We first make a fast move to accumulate some momentum and get the cut blade to the initial contact with the filament
# - We then make a slow move for the actual cut to happen 
variable_travel_speed: 166		# mm/s
variable_cut_fast_move_speed: 33	# mm/s
variable_cut_slow_move_speed: 8		# mm/s
variable_evacuate_speed: 166		# mm/s
variable_cut_dwell_time: 50		# Time to dwell at the cut point in ms
variable_cut_fast_move_fraction: 1.0	# Fraction of the move that uses fast move 

variable_extruder_move_speed: 1500	# mm/s for all extruder movement

# Safety margin for fast vs slow travel
# When traveling to the pin location, we make a safer but longer move if we are closer to the pin than this specified margin
# Usually setting these to the size of the toolhead (plus a small margin) should be good enough 
variable_safe_margin_x: 30
variable_safe_margin_y: 30

# If gantry servo option is installed, enable the servo and set up and down angle positions
variable_gantry_servo_enabled: 0	# 1 = enabled, 0 = disabled
variable_gantry_servo_down_angle: 55
variable_gantry_servo_up_angle: 180

# -------------------------- Internal Don't Touch -------------------------
variable_final_eject: 0			# MMU_TEST_FORM_TIP testing only: Whether to eject the filament at the end
variable_output_park_pos: 0		# Dynamically set in macro

gcode:
    {% set RETRACT_LENGTH = params.RETRACT_LENGTH | default(printer['gcode_macro _MMU_CUT_TIP']['retract_length']) | float %}
    {% set PUSHBACK_LENGTH = params.PUSHBACK_LENGTH | default(printer['gcode_macro _MMU_CUT_TIP']['pushback_length']) | float %}
    {% set FINAL_EJECT = params.FINAL_EJECT | default(printer['gcode_macro _MMU_CUT_TIP']['final_eject']) | int %}

    SAVE_GCODE_STATE NAME=_MMU_CUT_TIP_state

    {% if ("xy" not in printer.toolhead.homed_axes) %}
        G28 X Y
    {% endif %}

    SET_PRESSURE_ADVANCE ADVANCE=0 # Temporarily disable pressure advance. Happy Hare will restore it

    {% set pin_park_x_loc = pin_loc_x + pin_park_x_dist %}
    {% set pin_park_y_loc = pin_loc_y %}

    G90 # Absolute positioning
    M83 # Relative extrusion
    G92 E0
    {% if RETRACT_LENGTH > 0 %}
        # Retract to save filament waste, repeat to allow some cooling
        G1 E-{RETRACT_LENGTH} F{extruder_move_speed|float * 60}
        G1 E{RETRACT_LENGTH / 2} F{extruder_move_speed|float * 60} 
        G1 E-{RETRACT_LENGTH / 2} F{extruder_move_speed|float * 60}
    {% endif %}

    _FILAMETRIX_GANTRY_SERVO_UP
    _FILAMETRIX_MOVE_TO_CUTTER_PIN PIN_PARK_X_LOC={pin_park_x_loc} PIN_PARK_Y_LOC={pin_park_y_loc}
    _FILAMETRIX_GANTRY_SERVO_DOWN
    _FILAMETRIX_DO_CUT_MOTION PIN_PARK_X_LOC={pin_park_x_loc} RIP_LENGTH={rip_length}
    _FILAMETRIX_GANTRY_SERVO_UP

    # Optionally pushback of the tip residual into the hotend to avoid future catching
    {% if PUSHBACK_LENGTH > 0 %} 
        G1 E{PUSHBACK_LENGTH} F{extruder_move_speed|float * 60} 
        G4 P{pushback_dwell_time}
        G1 E-{PUSHBACK_LENGTH} F{extruder_move_speed|float * 60}
    {% endif %}
    
    # Final eject is for testing
    {% if FINAL_EJECT == 1 %}
        G1 E-80 F{extruder_move_speed|float * 60}
    {% endif %}

    # Dynamically set the required output variables for Happy Hare
    {% set park_pos = blade_pos + rip_length %}
    SET_GCODE_VARIABLE MACRO=_MMU_CUT_TIP VARIABLE=output_park_pos VALUE={park_pos}

    # Restore state and optionally position (usually on wipetower)
    RESTORE_GCODE_STATE NAME=_MMU_CUT_TIP_state MOVE={restore_position} MOVE_SPEED={travel_speed}


###########################################################################
# Helper macro for tip cutting
#
[gcode_macro _FILAMETRIX_MOVE_TO_CUTTER_PIN]
description: Helper to move the toolhead to the target pin in either safe or faster way, depending on toolhead clearance
gcode:
    {% set safe_margin_x = printer['gcode_macro _MMU_CUT_TIP']['safe_margin_x'] | float %}
    {% set safe_margin_y = printer['gcode_macro _MMU_CUT_TIP']['safe_margin_y'] | float %}
    {% set travel_speed = printer['gcode_macro _MMU_CUT_TIP']['travel_speed'] | float %}
    {% set pin_park_x_loc = params.PIN_PARK_X_LOC | float %}
    {% set pin_park_y_loc = params.PIN_PARK_Y_LOC | float %}

    {% if ((printer.gcode_move.gcode_position.x - pin_park_x_loc) | abs < safe_margin_x) or ((printer.gcode_move.gcode_position.y - pin_park_y_loc | float) | abs < safe_margin_y) %}
        # Make a safe but slower travel move
        G1 X{pin_park_x_loc} F{travel_speed|float * 60}
        G1 Y{pin_park_y_loc} F{travel_speed|float * 60}
    {% else %}
        G1 X{pin_park_x_loc} Y{pin_park_y_loc} F{travel_speed|float * 60}
    {% endif %}


###########################################################################
# Helper macro for tip cutting
#
[gcode_macro _FILAMETRIX_DO_CUT_MOTION]
description: Helper to do a single horizontal cut movement
gcode:
    {% set pin_loc_x_compressed = printer['gcode_macro _MMU_CUT_TIP']['pin_loc_x_compressed'] | float %}
    {% set cut_fast_move_fraction = printer['gcode_macro _MMU_CUT_TIP']['cut_fast_move_fraction'] | float %}
    {% set cut_fast_move_speed = printer['gcode_macro _MMU_CUT_TIP']['cut_fast_move_speed'] | float %}
    {% set cut_slow_move_speed = printer['gcode_macro _MMU_CUT_TIP']['cut_slow_move_speed'] | float %}
    {% set cut_dwell_time = printer['gcode_macro _MMU_CUT_TIP']['cut_dwell_time'] | float %}
    {% set evacuate_speed = printer['gcode_macro _MMU_CUT_TIP']['evacuate_speed'] | float %}
    {% set pin_park_x_loc = params.PIN_PARK_X_LOC | float %}
    {% set rip_speed = printer['gcode_macro _MMU_CUT_TIP']['rip_speed'] | float %}
    {% set rip_length = params.RIP_LENGTH | default(1) | float %}
    {% set fast_slow_transition_loc = (pin_loc_x_compressed - pin_park_x_loc) * cut_fast_move_fraction + pin_park_x_loc | float %}

    G1 X{fast_slow_transition_loc} F{cut_fast_move_speed|float * 60} # Make a fast move to initiate contact of the blade with the filament
    G1 X{pin_loc_x_compressed} F{cut_slow_move_speed|float * 60} # Do the cut in slow move
    G4 P{cut_dwell_time}
    {% if rip_length > 0 %}
        G1 E-{rip_length} F{rip_speed|float * 60}
    {% endif %}
    G1 X{pin_park_x_loc} F{evacuate_speed|float * 60} # Evacuate


###########################################################################
# Helper macro for tip cutting
#
[gcode_macro _FILAMETRIX_GANTRY_SERVO_DOWN]
description: Operate optional gantry servo operated pin
gcode:
    {% if printer['gcode_macro _MMU_CUT_TIP']['gantry_servo_enabled'] > 0 %}
        {% set ANGLE = printer['gcode_macro _MMU_CUT_TIP']['gantry_servo_down_angle'] | float %}
        SET_SERVO SERVO=mmu_gantry_servo ANGLE={ANGLE}
    {% endif %}


###########################################################################
# Helper macro for tip cutting
#
[gcode_macro _FILAMETRIX_GANTRY_SERVO_UP]
description: Operate optional gantry servo operated pin
gcode:
    {% if printer['gcode_macro _MMU_CUT_TIP']['gantry_servo_enabled'] > 0 %}
        {% set ANGLE = printer['gcode_macro _MMU_CUT_TIP']['gantry_servo_up_angle'] | float %}
        SET_SERVO SERVO=mmu_gantry_servo ANGLE={ANGLE}
    {% endif %}

